// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2018-2020.
 * Description:
 * Author: Lai Hangliang
 * Create: 2023-10-11
 */

#include <linux/nmi.h>
#include <linux/mm.h>
#include <linux/cpu.h>
#include <linux/init.h>
#include <linux/kprobes.h>
#include <linux/module.h>
#include <linux/sysctl.h>
#include <linux/tick.h>
#include <linux/sched/clock.h>
#include <linux/sched/debug.h>
#include <linux/sched/isolation.h>
#include <linux/stop_machine.h>

#include <linux/percpu.h>
#include <asm/irq_regs.h>
#include <linux/kvm_para.h>

#include <linux/watchdog_enhance.h>

#ifdef CONFIG_EULEROS_WATCHDOG_ENHANCE_FEATRUE
int __read_mostly watchdog_enhance_enable = 1;
unsigned long __read_mostly wdt_touch_detector;
int __read_mostly watchdog_print_period = 10;
int __read_mostly watchdog_softlockup_divide = 5;
static DEFINE_PER_CPU(unsigned long, wdt_last_arrival_ts);
static DEFINE_PER_CPU(unsigned long, watchdog_print_timestamp);

static int __init wdt_touch_detector_setup(char *str)
{
	int err = 0;

	err = kstrtoul(str, 0, &wdt_touch_detector);
	if (err)
		return err;
	return 1;
}
__setup("wdt_touch_detector=", wdt_touch_detector_setup);


static unsigned long watchdog_get_print_timestamp(int cpu)
{
	return per_cpu(watchdog_print_timestamp, cpu);
}

static void watchdog_set_print_timestamp(int cpu, unsigned long timestamp)
{
	per_cpu(watchdog_print_timestamp, cpu) = timestamp;
}

static void watchdog_print_info(unsigned long duration)
{
	pr_emerg("WARN: soft lockup - CPU#%d stuck for %lus! [%s:%d]\n",
		smp_processor_id(), duration,
		current->comm, task_pid_nr(current));
	dump_stack();
}

uint64_t watchdog_set_sample_period(uint64_t sample_period)
{
	if (watchdog_enhance_enable) {
		sample_period =
			get_softlockup_thresh() *
			((u64)NSEC_PER_SEC / watchdog_softlockup_divide);
		pr_info("watchdog: sample period is: %llu\n", sample_period);
	} else {
		sample_period = get_softlockup_thresh() * ((u64)NSEC_PER_SEC / 5);
	}
	return sample_period;
}

void watchdog_process_before_softlockup(unsigned long touch_ts)
{
	unsigned long now = get_timestamp();
	unsigned long last_print_timestamp;
	int cpu = smp_processor_id();

	if (!watchdog_enhance_enable)
		return;

	if (watchdog_print_period > 0
		&& time_after(now, touch_ts + watchdog_print_period)) {
		last_print_timestamp = watchdog_get_print_timestamp(cpu);
		if (time_before(now, last_print_timestamp + watchdog_print_period))
			return;

		watchdog_set_print_timestamp(cpu, now);
		watchdog_print_info(now - touch_ts);
	}
}

void watchdog_touch_in_softlockup_check(struct pt_regs *regs)
{
	unsigned long now = get_timestamp();
	int this_cpu = smp_processor_id();
	unsigned long touch_ts;

	if (wdt_touch_detector) {
		touch_ts = __this_cpu_read(wdt_last_arrival_ts);
		if (now > touch_ts + watchdog_thresh * 4) {
			pr_emerg("Detected touching watchdog in LOCKUP on cpu %d for %lus",
					this_cpu, now - touch_ts);
			pr_emerg("PID: %d Comm: %s State: %c Switches: %lu",
					task_pid_nr(current), current->comm,
					task_state_to_char(current),
					current->nvcsw + current->nivcsw);

			print_modules();
			print_irqtrace_events(current);
			if (regs)
				show_regs(regs);
			else
				dump_stack();

			if (hardlockup_panic)
				nmi_panic(regs, "LOCKUP");
		}
	}
}

int watchdog_set_print_period(int period)
{
	int cpu;

	if (period < 1 || period > 60)
		return -EINVAL;

	mutex_lock(&watchdog_mutex);

	watchdog_print_period = period;
	pr_info("[%s]set watchdog_print_period to %d\n", __func__, period);
	for_each_online_cpu(cpu)
		per_cpu(watchdog_touch_ts, cpu) = get_timestamp();

	mutex_unlock(&watchdog_mutex);
	return 0;
}
EXPORT_SYMBOL(watchdog_set_print_period);

int watchdog_set_softlockup_divide(int divide)
{
	if (divide < 1 || divide > 60)
		return -EINVAL;

	mutex_lock(&watchdog_mutex);

	watchdog_softlockup_divide = divide;
	pr_info("[%s]set watchdog_softlockup_divide to %d\n", __func__, divide);
	set_sample_period();

	mutex_unlock(&watchdog_mutex);
	return 0;
}
EXPORT_SYMBOL(watchdog_set_softlockup_divide);

int proc_watchdog_enhance_enable(struct ctl_table *table, int write,
				void __user *buffer, size_t *lenp, loff_t *ppos)
{
	int err, old;

	mutex_lock(&watchdog_mutex);

	old = READ_ONCE(watchdog_enhance_enable);
	err = proc_dointvec_minmax(table, write, buffer, lenp, ppos);

	if (!err && write && old != READ_ONCE(watchdog_enhance_enable)) {
		if (watchdog_enhance_enable) {
			pr_info("please wait %llus for show cpu and irq stat accurately\n",
				watchdog_enhance_get_sample_period() / NSEC_PER_SEC * 2);
			}
	}

	mutex_unlock(&watchdog_mutex);
	return err;
}

int proc_watchdog_softlockup_divide(struct ctl_table *table, int write,
				    void __user *buffer, size_t *lenp, loff_t *ppos)
{
	int err, old;

	mutex_lock(&watchdog_mutex);

	old = READ_ONCE(watchdog_softlockup_divide);
	err = proc_dointvec_minmax(table, write, buffer, lenp, ppos);

	if (!err && write && old != READ_ONCE(watchdog_softlockup_divide))
		set_sample_period();

	mutex_unlock(&watchdog_mutex);
	return err;
}

void update_wdt_last_arrival_ts(void)
{
	__this_cpu_write(wdt_last_arrival_ts, get_timestamp());
}
#endif
